Skip to content

Clean up <ThemeProvider> and various CSS related stuff#5611

Merged
compulim merged 63 commits intomicrosoft:mainfrom
compulim:feat-clean-up-theme-provider
Oct 14, 2025
Merged

Clean up <ThemeProvider> and various CSS related stuff#5611
compulim merged 63 commits intomicrosoft:mainfrom
compulim:feat-clean-up-theme-provider

Conversation

@compulim
Copy link
Copy Markdown
Contributor

@compulim compulim commented Oct 8, 2025

Changelog Entry

Added

  • Cleaned up <ThemeProvider> and various CSS related code, in PR #5611, by @compulim

Description

This is for paving the way to host a new experience working side-by-side with existing experience. We need CSS Modules to work with the new experience.

Also took some time to audit the usage of nonce and clean it up so it's easier to audit.

Design

Note: this section need to be updated.

New <StyleOptionsComposer>

Slowly refactoring things out of <Composer>. <Composer> is still the root of everything (except <ThemeProvider> which could be parent of <Composer>.)

All rectification logic is being refactored. We should slowly revisit it by using valibot to fill optional fields and for rectifications.

The current rectification logic does not take cascading into account. We need some modifications.

Hook vs component

useInjectStyles is replaced by <InjectStyleElements> for cleaner DOM related manipulation.

  • Component likely lead to DOM change
  • Hook unlikely lead to DOM change, despite the DOM is outside of React tree
  • <InjectStyleElements> is a leaf node component without children props
  • <InjectStyleElements> will handle duplications, including handling same instance of <link rel="stylesheet">/<style> with and without nonce
  • Dedupe is best effort, if the element is being injected twice, the instance will be moved to a latter position to make sure it is cascaded properly

As a result, useCSSCustomProperties() is also becoming <CustomPropertiesContainer>.

CSS injection order

Instead of saving all CSS injections into context, to save some logics, we are injecting CSS when the <InjectStyleElements> is mounted. Ordering is important.

<InjectStyleElementsComposer styleElements={[stylesA1, stylesA2]}>
  <InjectStyleElementsComposer styleElements={[stylesB1, stylesA1]} />
</InjectStyleElementsComposer>

Will be injected in the following order to honor CSS cascading:

<head>
  <style>... stylesA2 ...</style>
  <style>... stylesB1 ...</style>
  <style>... stylesA1 ...</style>
</head>

Note:

  • Deeper component has higher priority, will be injected later or moved to a latter position
    • stylesB1 is after stylesA2 because it is deeper in the tree
    • stylesA1 get moved after stylesB1 because of dedupe while keeping "deeper element is injected later"

Also thought about using createPortal() to inject DOM, but it is not available in React 16.8.6.

Fixed bugs

  • When mounting the same parented <style> element twice, it should not mount it twice
  • Some normalization/rectification logic for style options, especially those logic that handle deprecated options, need to be rewrite so they handle cascading properly

Specific Changes

  • Refactored <StyleOptionsComposer> out of <API.Composer>
  • Renames
    • <WebChatTheme> -> <ComponentStylesheet>/<DecoratorStylesheet>
    • createStyles -> createComponentStyleElements/createDecoratorStyleElements
  • useInjectStyleElements(styleElements) -> <InjectStyleElements nonce={} styleElements={}>
    • useCSSCustomProperties() -> <CSSCustomPropertiesContainer>
  • Fixed --watch 200
    • esbuild --watch --watch-delay 200
    • tsup.watch({ delay: 200 })
  • I have added tests and executed them locally
  • I have updated CHANGELOG.md
  • I have updated documentation

Review Checklist

This section is for contributors to review your work.

  • Accessibility reviewed (tab order, content readability, alt text, color contrast)
  • Browser and platform compatibilities reviewed
  • CSS styles reviewed (minimal rules, no z-index)
  • Documents reviewed (docs, samples, live demo)
  • Internationalization reviewed (strings, unit formatting)
  • package.json and package-lock.json reviewed
  • Security reviewed (no data URIs, check for nonce leak)
  • Tests reviewed (coverage, legitimacy)

Comment thread packages/bundle/src/boot/actual/internal.ts
Comment thread packages/component/src/providers/Theme/private/Context.ts
Comment thread packages/component/src/providers/Theme/ThemeProvider.tsx
Comment thread __tests__/html2/themeProvider/override.html
Comment thread packages/fluent-theme/src/private/FluentThemeProvider.tsx
Comment thread __tests__/html/hooks.useInjectStyles.changeNonce.html
Comment thread __tests__/html2/provider/InjectStylesElementsComposer/dupeElement.withParent.html Outdated
@OEvgeny
Copy link
Copy Markdown
Collaborator

OEvgeny commented Oct 8, 2025

While we're not on the path to implement CSSStyleSheet and using document.adoptedStyleSheets due to some devtools quirks and compatibility issues, I think we should keep it in mind when dealing with CSS.

Previous implementation was fairly easy to update to the pattern. How about this one?

Comment thread packages/component/src/cssContent.ts Outdated
Comment thread packages/component/src/Styles/InjectCSS.tsx Outdated
@compulim
Copy link
Copy Markdown
Contributor Author

compulim commented Oct 9, 2025

While we're not on the path to implement CSSStyleSheet and using document.adoptedStyleSheets due to some devtools quirks and compatibility issues, I think we should keep it in mind when dealing with CSS.

Previous implementation was fairly easy to update to the pattern. How about this one?

Agree, dev tools don't show the CSS is a huge obstacles for debugging.

For 3P, all CSS injections will be done by <ThemeProvider>. In future, we can introduce <ThemeProvider styleSheet={new CSSStyleSheet}> to support CSSStyleSheet.

Inside <ThemeProvider>, it use <InjectStyleElementsComposer>. 1P white-label also use <ISEC>.

To support CSSStyleSheet, it should be updated with <ISEC styleSheet={new CSSStyleSheet}> as well. Then, everyone can use either HTML*Element or CSSStyleSheet.

Comment thread packages/fluent-theme/src/components/theme/Theme.tsx
Comment thread packages/component/src/stylesheet/DecoratorCSS.tsx Outdated
@compulim compulim marked this pull request as ready for review October 12, 2025 09:10
@compulim compulim changed the title [WIP] Clean up <ThemeProvider> and various CSS related stuff Clean up <ThemeProvider> and various CSS related stuff Oct 13, 2025
Comment thread packages/styles/src/react/private/InjectStyleElements.tsx
OEvgeny
OEvgeny previously approved these changes Oct 13, 2025
OEvgeny
OEvgeny previously approved these changes Oct 14, 2025
@compulim compulim merged commit 6ee0a58 into microsoft:main Oct 14, 2025
26 checks passed
@compulim compulim deleted the feat-clean-up-theme-provider branch October 14, 2025 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants